/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/linked_hash_table-inl.h"
#include "plugins/ecmascript/runtime/linked_hash_table.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/tagged_hash_table-inl.h"
#include "plugins/ecmascript/tests/runtime/common/test_helper.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

// NOLINTBEGIN(readability-magic-numbers,modernize-avoid-c-arrays)

namespace ark::test {
class LinkedHashTableTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

    JSHandle<GlobalEnv> GetGlobalEnv()
    {
        EcmaVM *ecma = thread_->GetEcmaVM();
        return ecma->GetGlobalEnv();
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

private:
    PandaVM *instance_ {nullptr};
    EcmaHandleScope *scope_ {nullptr};
};

TEST_F(LinkedHashTableTest, MapCreate)
{
    int numOfElement = 64;
    JSHandle<LinkedHashMap> dict = LinkedHashMap::Create(thread_, numOfElement);
    EXPECT_TRUE(*dict != nullptr);
}

TEST_F(LinkedHashTableTest, SetCreate)
{
    int numOfElement = 64;
    JSHandle<LinkedHashSet> set = LinkedHashSet::Create(thread_, numOfElement);
    EXPECT_TRUE(*set != nullptr);
}

TEST_F(LinkedHashTableTest, addKeyAndValue)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // mock object needed in test
    int numOfElement = 64;
    JSHandle<LinkedHashMap> dictHandle = LinkedHashMap::Create(thread_, numOfElement);
    EXPECT_TRUE(*dictHandle != nullptr);
    JSHandle<JSTaggedValue> objFun = GetGlobalEnv()->GetObjectFunction();

    char keyArray[] = "hello";
    JSHandle<EcmaString> stringKey1 = factory->NewFromCanBeCompressString(keyArray);
    JSHandle<JSTaggedValue> key1(stringKey1);
    JSHandle<JSTaggedValue> value1(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun));

    char key2Array[] = "hello2";
    JSHandle<EcmaString> stringKey2 = factory->NewFromCanBeCompressString(key2Array);
    JSHandle<JSTaggedValue> key2(stringKey2);
    JSHandle<JSTaggedValue> value2(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun));

    // test set()
    dictHandle = LinkedHashMap::Set(thread_, dictHandle, key1, value1);
    EXPECT_EQ(dictHandle->NumberOfElements(), 1);

    // test find()
    int hash = LinkedHash::Hash(key1.GetTaggedValue());
    int entry1 = dictHandle->FindElement(key1.GetTaggedValue(), hash);
    EXPECT_EQ(key1.GetTaggedValue(), dictHandle->GetKey(entry1));
    EXPECT_EQ(value1.GetTaggedValue(), dictHandle->GetValue(entry1));

    dictHandle = LinkedHashMap::Set(thread_, dictHandle, key2, value2);
    EXPECT_EQ(dictHandle->NumberOfElements(), 2);
    // test remove()
    dictHandle = LinkedHashMap::Delete(thread_, dictHandle, key1);
    EXPECT_EQ(-1, dictHandle->FindElement(key1.GetTaggedValue(), hash));
    EXPECT_EQ(dictHandle->NumberOfElements(), 1);

    JSHandle<JSTaggedValue> undefinedKey(thread_, JSTaggedValue::Undefined());
    dictHandle = LinkedHashMap::Set(thread_, dictHandle, undefinedKey, value1);
    int undefinedHash = LinkedHash::Hash(undefinedKey.GetTaggedValue());
    int entry2 = dictHandle->FindElement(undefinedKey.GetTaggedValue(), undefinedHash);
    EXPECT_EQ(value1.GetTaggedValue(), dictHandle->GetValue(entry2));
}

TEST_F(LinkedHashTableTest, SetaddKeyAndValue)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    // mock object needed in test
    int numOfElement = 64;
    JSHandle<LinkedHashSet> setHandle = LinkedHashSet::Create(thread_, numOfElement);
    EXPECT_TRUE(*setHandle != nullptr);
    JSHandle<JSTaggedValue> objFun = GetGlobalEnv()->GetObjectFunction();

    char keyArray[] = "hello";
    JSHandle<EcmaString> stringKey1 = factory->NewFromCanBeCompressString(keyArray);
    JSHandle<JSTaggedValue> key1(stringKey1);
    JSHandle<JSTaggedValue> value1(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun));

    char key2Array[] = "hello2";
    JSHandle<EcmaString> stringKey2 = factory->NewFromCanBeCompressString(key2Array);
    JSHandle<JSTaggedValue> key2(stringKey2);
    JSHandle<JSTaggedValue> value2(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun));

    // test set()
    setHandle = LinkedHashSet::Add(thread_, setHandle, key1);
    EXPECT_EQ(setHandle->NumberOfElements(), 1);

    // test has()
    int hash = LinkedHash::Hash(key1.GetTaggedValue());
    EXPECT_TRUE(setHandle->Has(key1.GetTaggedValue(), hash));

    setHandle = LinkedHashSet::Add(thread_, setHandle, key2);
    EXPECT_EQ(setHandle->NumberOfElements(), 2);
    // test remove()
    setHandle = LinkedHashSet::Delete(thread_, setHandle, key1);
    EXPECT_EQ(-1, setHandle->FindElement(key1.GetTaggedValue(), hash));
    EXPECT_EQ(setHandle->NumberOfElements(), 1);

    JSHandle<JSTaggedValue> undefinedKey(thread_, JSTaggedValue::Undefined());
    setHandle = LinkedHashSet::Add(thread_, setHandle, undefinedKey);
    int undefinedHash = LinkedHash::Hash(undefinedKey.GetTaggedValue());
    EXPECT_TRUE(setHandle->Has(undefinedKey.GetTaggedValue(), undefinedHash));
}

TEST_F(LinkedHashTableTest, GrowCapacity)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    int numOfElement = 8;
    JSHandle<LinkedHashMap> dictHandle = LinkedHashMap::Create(thread_, numOfElement);
    EXPECT_TRUE(*dictHandle != nullptr);
    JSHandle<JSFunction> objFun(GetGlobalEnv()->GetObjectFunction());
    char keyArray[7] = "hello";
    for (int i = 0; i < 33; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(keyArray));
        JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(i));

        // test insert()
        dictHandle = LinkedHashMap::Set(thread_, dictHandle, key, value);
        int hash = LinkedHash::Hash(key.GetTaggedValue());
        EXPECT_EQ(i, dictHandle->FindElement(key.GetTaggedValue(), hash));
    }

    // test order
    for (int i = 0; i < 33; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<EcmaString> stringKey = factory->NewFromCanBeCompressString(keyArray);
        // test insert()
        int hash = LinkedHash::Hash(stringKey.GetTaggedValue());
        EXPECT_EQ(i, dictHandle->FindElement(stringKey.GetTaggedValue(), hash));
    }
    EXPECT_EQ(dictHandle->NumberOfElements(), 33);
    EXPECT_EQ(dictHandle->Capacity(), 64);
}

TEST_F(LinkedHashTableTest, SetGrowCapacity)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    int numOfElement = 8;
    JSHandle<LinkedHashSet> setHandle = LinkedHashSet::Create(thread_, numOfElement);
    EXPECT_TRUE(*setHandle != nullptr);
    JSHandle<JSFunction> objFun(GetGlobalEnv()->GetObjectFunction());
    // create key and values
    char keyArray[7] = "hello";
    for (int i = 0; i < 33; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<EcmaString> stringKey = factory->NewFromCanBeCompressString(keyArray);
        JSHandle<JSTaggedValue> key(stringKey);

        // test insert()
        setHandle = LinkedHashSet::Add(thread_, setHandle, key);
        int hash = LinkedHash::Hash(key.GetTaggedValue());
        EXPECT_EQ(i, setHandle->FindElement(key.GetTaggedValue(), hash));
    }

    // test order
    for (int i = 0; i < 33; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<EcmaString> stringKey = factory->NewFromCanBeCompressString(keyArray);
        // test insert()
        int hash = LinkedHash::Hash(stringKey.GetTaggedValue());
        EXPECT_EQ(i, setHandle->FindElement(stringKey.GetTaggedValue(), hash));
    }
    EXPECT_EQ(setHandle->NumberOfElements(), 33);
    EXPECT_EQ(setHandle->Capacity(), 64);
}

TEST_F(LinkedHashTableTest, ShrinkCapacity)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    int numOfElement = 64;
    JSHandle<LinkedHashMap> dictHandle = LinkedHashMap::Create(thread_, numOfElement);
    EXPECT_TRUE(*dictHandle != nullptr);
    JSHandle<JSFunction> objFun(GetGlobalEnv()->GetObjectFunction());
    char keyArray[7] = "hello";
    for (int i = 0; i < 10; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(keyArray));
        JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(i));

        // test insert()
        dictHandle = LinkedHashMap::Set(thread_, dictHandle, key, value);
    }
    keyArray[5] = '1' + 9;
    JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(keyArray));
    dictHandle = LinkedHashMap::Delete(thread_, dictHandle, key);
    // test order
    for (int i = 0; i < 9; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<EcmaString> stringKey = factory->NewFromCanBeCompressString(keyArray);
        // test insert()
        int hash = LinkedHash::Hash(stringKey.GetTaggedValue());
        EXPECT_EQ(i, dictHandle->FindElement(stringKey.GetTaggedValue(), hash));
    }
    EXPECT_EQ(dictHandle->NumberOfElements(), 9);
    EXPECT_EQ(dictHandle->Capacity(), 16);
}

TEST_F(LinkedHashTableTest, SetShrinkCapacity)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    int numOfElement = 64;
    JSHandle<LinkedHashSet> setHandle = LinkedHashSet::Create(thread_, numOfElement);
    EXPECT_TRUE(*setHandle != nullptr);
    JSHandle<JSFunction> objFun(GetGlobalEnv()->GetObjectFunction());
    // create key and values
    char keyArray[7] = "hello";
    for (int i = 0; i < 10; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(keyArray));

        // test insert()
        setHandle = LinkedHashSet::Add(thread_, setHandle, key);
    }
    keyArray[5] = '1' + 9;
    JSHandle<JSTaggedValue> keyHandle(factory->NewFromCanBeCompressString(keyArray));
    setHandle = LinkedHashSet::Delete(thread_, setHandle, keyHandle);
    // test order
    for (int i = 0; i < 9; i++) {
        keyArray[5] = '1' + i;
        keyArray[6] = 0;
        JSHandle<EcmaString> stringKey = factory->NewFromCanBeCompressString(keyArray);
        // test insert()
        int hash = LinkedHash::Hash(stringKey.GetTaggedValue());
        EXPECT_EQ(i, setHandle->FindElement(stringKey.GetTaggedValue(), hash));
    }
    EXPECT_EQ(setHandle->NumberOfElements(), 9);
    EXPECT_EQ(setHandle->Capacity(), 16);
}
}  // namespace ark::test

// NOLINTEND(readability-magic-numbers,modernize-avoid-c-arrays)
